// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once

//-----------------------------------------------------------------------------
//  Package Title  ratpak
//  File           ratpak.h
//  Copyright      (C) 1995-99 Microsoft
//  Date           01-16-95
//
//
//  Description
//
//     Infinite precision math package header file, if you use ratpak.lib you
//  need to include this header.
//
//-----------------------------------------------------------------------------

#include "CalcErr.h"

static constexpr uint32_t BASEXPWR = 31L;// Internal log2(BASEX)
static constexpr uint32_t BASEX = 0x80000000; // Internal radix used in calculations, hope to raise
                        // this to 2^32 after solving scaling problems with
                        // overflow detection esp. in mul

typedef unsigned long MANTTYPE;
typedef unsigned __int64 TWO_MANTTYPE;

enum eNUMOBJ_FMT {
    FMT_FLOAT,        // returns floating point, or exponential if number is too big
    FMT_SCIENTIFIC,    // always returns scientific notation
    FMT_ENGINEERING    // always returns engineering notation such that exponent is a multiple of 3

};

enum eANGLE_TYPE {
    ANGLE_DEG,    // Calculate trig using 360 degrees per revolution
    ANGLE_RAD,    // Calculate trig using 2 pi  radians per revolution
    ANGLE_GRAD    // Calculate trig using 400 gradients per revolution

};

typedef enum eNUMOBJ_FMT NUMOBJ_FMT;
typedef enum eANGLE_TYPE ANGLE_TYPE;

//-----------------------------------------------------------------------------
//
//  NUMBER type is a representation of a generic sized generic radix number
//
//-----------------------------------------------------------------------------

#pragma warning(push)
#pragma warning(disable:4200)   // nonstandard extension used : zero-sized array in struct/union
typedef struct _number
{
    long sign;        // The sign of the mantissa, +1, or -1
    long cdigit;    // The number of digits, or what passes for digits in the
                    // radix being used.
    long exp;       // The offset of digits from the radix point
                    // (decimal point in radix 10)
    MANTTYPE mant[];
                    // This is actually allocated as a continuation of the
                    // NUMBER structure.
} NUMBER, *PNUMBER, **PPNUMBER;
#pragma warning(pop)


//-----------------------------------------------------------------------------
//
//  RAT type is a representation radix  on 2 NUMBER types.
//  pp/pq, where pp and pq are pointers to integral NUMBER types.
//
//-----------------------------------------------------------------------------

typedef struct _rat
    {
    PNUMBER pp;
    PNUMBER pq;
    } RAT, *PRAT;

static constexpr uint32_t MAX_LONG_SIZE = 33; // Base 2 requires 32 'digits'

//-----------------------------------------------------------------------------
//
// List of useful constants for evaluation, note this list needs to be
// initialized.
//
//-----------------------------------------------------------------------------

extern PNUMBER num_one;
extern PNUMBER num_two;
extern PNUMBER num_five;
extern PNUMBER num_six;
extern PNUMBER num_ten;

extern PRAT ln_ten;
extern PRAT ln_two;
extern PRAT rat_zero;
extern PRAT rat_neg_one;
extern PRAT rat_one;
extern PRAT rat_two;
extern PRAT rat_six;
extern PRAT rat_half;
extern PRAT rat_ten;
extern PRAT pt_eight_five;
extern PRAT pi;
extern PRAT pi_over_two;
extern PRAT two_pi;
extern PRAT one_pt_five_pi;
extern PRAT e_to_one_half;
extern PRAT rat_exp;
extern PRAT rad_to_deg;
extern PRAT rad_to_grad;
extern PRAT rat_qword;
extern PRAT rat_dword;
extern PRAT rat_word;
extern PRAT rat_byte;
extern PRAT rat_360;
extern PRAT rat_400;
extern PRAT rat_180;
extern PRAT rat_200;
extern PRAT rat_nRadix;
extern PRAT rat_smallest;
extern PRAT rat_negsmallest;
extern PRAT rat_max_exp;
extern PRAT rat_min_exp;
extern PRAT rat_max_fact;
extern PRAT rat_min_fact;
extern PRAT rat_max_long;
extern PRAT rat_min_long;

// DUPNUM Duplicates a number taking care of allocation and internals
#define DUPNUM(a,b) destroynum(a);createnum( a, (b)->cdigit );_dupnum(a, b);

// DUPRAT Duplicates a rational taking care of allocation and internals
#define DUPRAT(a,b) destroyrat(a);createrat(a);DUPNUM((a)->pp,(b)->pp);DUPNUM((a)->pq,(b)->pq);

// LOG*RADIX calculates the integral portion of the log of a number in
// the base currently being used, only accurate to within g_ratio

#define LOGNUMRADIX(pnum) (((pnum)->cdigit+(pnum)->exp)*g_ratio)
#define LOGRATRADIX(prat) (LOGNUMRADIX((prat)->pp)-LOGNUMRADIX((prat)->pq))

// LOG*2 calculates the integral portion of the log of a number in
// the internal base being used, only accurate to within g_ratio

#define LOGNUM2(pnum) ((pnum)->cdigit+(pnum)->exp)
#define LOGRAT2(prat) (LOGNUM2((prat)->pp)-LOGNUM2((prat)->pq))

#if defined( DEBUG_RATPAK )
//-----------------------------------------------------------------------------
//
//   Debug versions of rational number creation and destruction routines.
//   used for debugging allocation errors.
//
//-----------------------------------------------------------------------------

#define createrat(y) (y)=_createrat(); \
{ \
    std::wstringstream outputString; \
    outputString << "createrat " << y << " " << # y << " file= " << __FILE__ << ", line= " << __LINE__ << "\n"; \
    OutputDebugString(outputString.str().c_str()); \
}
#define destroyrat(x) \
{ \
    std::wstringstream outputString; \
    outputString << "destroyrat " << x << " file= " << __FILE__ << ", line= " << __LINE__ << "\n"; \
    OutputDebugString(outputString.str().c_str()); \
} \
_destroyrat(x),(x)=nullptr
#define createnum(y,x) (y)=_createnum(x); \
{ \
    std::wstringstream outputString; \
    outputString << "createnum " << y << " " << # y << " file= " << __FILE__ << ", line= " << __LINE__ << "\n"; \
    OutputDebugString(outputString.str().c_str()); \
}
#define destroynum(x) \
{ \
    std::wstringstream outputString; \
    outputString << "destroynum " << x << " file= " << __FILE__ << ", line= " << __LINE__ << "\n"; \
    OutputDebugString(outputString.str().c_str()); \
} \
_destroynum(x),(x)=nullptr
#else
#define createrat(y) (y)=_createrat()
#define destroyrat(x) _destroyrat(x),(x)=nullptr
#define createnum(y,x) (y)=_createnum(x)
#define destroynum(x) _destroynum(x),(x)=nullptr
#endif

//-----------------------------------------------------------------------------
//
//   Defines for checking when to stop taylor series expansions due to
//   precision satisfaction.
//
//-----------------------------------------------------------------------------

// RENORMALIZE, gets the exponents non-negative.
#define RENORMALIZE(x) if ( (x)->pp->exp < 0 ) { \
    (x)->pq->exp -= (x)->pp->exp; \
    (x)->pp->exp = 0; \
    } \
    if ( (x)->pq->exp < 0 ) { \
    (x)->pp->exp -= (x)->pq->exp; \
    (x)->pq->exp = 0; \
    }

// TRIMNUM ASSUMES the number is in radix form NOT INTERNAL BASEX!!!
#define TRIMNUM(x, precision) if ( !g_ftrueinfinite ) { \
        long trim = (x)->cdigit - precision-g_ratio;\
            if ( trim > 1 ) \
                { \
memmove( (x)->mant, &((x)->mant[trim]), sizeof(MANTTYPE)*((x)->cdigit-trim) ); \
                (x)->cdigit -= trim; \
                (x)->exp += trim; \
                } \
            }
// TRIMTOP ASSUMES the number is in INTERNAL BASEX!!!
#define TRIMTOP(x, precision) if ( !g_ftrueinfinite ) { \
        long trim = (x)->pp->cdigit - (precision/g_ratio) - 2;\
            if ( trim > 1 ) \
                { \
memmove( (x)->pp->mant, &((x)->pp->mant[trim]), sizeof(MANTTYPE)*((x)->pp->cdigit-trim) ); \
                (x)->pp->cdigit -= trim; \
                (x)->pp->exp += trim; \
                } \
            trim = min((x)->pp->exp,(x)->pq->exp);\
            (x)->pp->exp -= trim;\
            (x)->pq->exp -= trim;\
            }

#define SMALL_ENOUGH_RAT(a, precision) (zernum((a)->pp) || ( ( ( (a)->pq->cdigit + (a)->pq->exp ) - ( (a)->pp->cdigit + (a)->pp->exp ) - 1 ) * g_ratio > precision ) )

//-----------------------------------------------------------------------------
//
//   Defines for setting up taylor series expansions for infinite precision
//   functions.
//
//-----------------------------------------------------------------------------

#define CREATETAYLOR() PRAT xx=nullptr;\
    PNUMBER n2=nullptr; \
    PRAT pret=nullptr; \
    PRAT thisterm=nullptr; \
    DUPRAT(xx,*px); \
    mulrat(&xx,*px, precision); \
    createrat(pret); \
    pret->pp=longtonum( 0L, BASEX ); \
    pret->pq=longtonum( 0L, BASEX );

#define DESTROYTAYLOR() destroynum( n2 ); \
    destroyrat( xx );\
    destroyrat( thisterm );\
    destroyrat( *px );\
    trimit(&pret, precision);\
    *px=pret;

// INC(a) is the rational equivalent of a++
// Check to see if we can avoid doing this the hard way.
#define INC(a) if ( (a)->mant[0] < BASEX - 1 ) \
    { \
    (a)->mant[0]++; \
    } \
    else \
    { \
    addnum( &(a), num_one, BASEX); \
    }

#define MSD(x) ((x)->mant[(x)->cdigit-1])
// MULNUM(b) is the rational equivalent of thisterm *= b where thisterm is
// a rational and b is a number, NOTE this is a mixed type operation for
// efficiency reasons.
#define MULNUM(b) mulnumx( &(thisterm->pp), b);

// DIVNUM(b) is the rational equivalent of thisterm /= b where thisterm is
// a rational and b is a number, NOTE this is a mixed type operation for
// efficiency reasons.
#define DIVNUM(b) mulnumx( &(thisterm->pq), b);

// NEXTTERM(p,d) is the rational equivalent of
// thisterm *= p
// d    <d is usually an expansion of operations to get thisterm updated.>
// pret += thisterm
#define NEXTTERM(p,d,precision) mulrat(&thisterm,p,precision);d addrat( &pret, thisterm, precision )

//-----------------------------------------------------------------------------
//
//   External variables used in the math package.
//
//-----------------------------------------------------------------------------

extern bool g_ftrueinfinite; // set to true to allow infinite precision
                             // don't use unless you know what you are doing
                      // used to help decide when to stop calculating.

extern long g_ratio;  // Internally calculated ratio of internal radix

//-----------------------------------------------------------------------------
//
//   External functions defined in the math package.
//
//-----------------------------------------------------------------------------

// Call whenever decimal separator character changes.
extern void SetDecimalSeparator(wchar_t decimalSeparator);

// Call whenever either radix or precision changes, is smarter about recalculating constants.
extern void ChangeConstants(uint32_t radix, int32_t precision);

extern bool equnum(_In_ PNUMBER a, _In_ PNUMBER b );     // returns true of a == b
extern bool lessnum(_In_ PNUMBER a, _In_ PNUMBER b );    // returns true of a < b
extern bool zernum(_In_ PNUMBER a );                // returns true of a == 0
extern bool zerrat(_In_ PRAT a );                   // returns true if a == 0/q
extern std::wstring NumberToString(_Inout_ PNUMBER& pnum, int format, uint32_t radix, int32_t precision);

// returns a text representation of a PRAT
extern std::wstring RatToString(_Inout_ PRAT& prat, int format, uint32_t radix, int32_t precision);
// converts a PRAT into a PNUMBER
extern PNUMBER RatToNumber(_In_ PRAT prat, uint32_t radix, int32_t precision);
// flattens a PRAT by converting it to a PNUMBER and back to a PRAT
extern void flatrat(_Inout_ PRAT& prat, uint32_t radix, int32_t precision);

extern long numtolong(_In_ PNUMBER pnum, uint32_t radix );
extern long rattolong(_In_ PRAT prat, uint32_t radix, int32_t precision);
ULONGLONG rattoUlonglong(_In_ PRAT prat, uint32_t radix, int32_t precision);
extern PNUMBER _createnum(_In_ ULONG size ); // returns an empty number structure with size digits
extern PNUMBER nRadixxtonum(_In_ PNUMBER a, uint32_t radix, int32_t precision);
extern PNUMBER gcd(_In_ PNUMBER a, _In_ PNUMBER b );
extern PNUMBER StringToNumber(std::wstring_view numberString, uint32_t radix, int32_t precision); // takes a text representation of a number and returns a number.

// takes a text representation of a number as a mantissa with sign and an exponent with sign.
extern PRAT StringToRat(bool mantissaIsNegative, std::wstring_view mantissa, bool exponentIsNegative, std::wstring_view exponent, uint32_t radix, int32_t precision);

extern PNUMBER longfactnum(long inlong, uint32_t radix);
extern PNUMBER longprodnum(long start, long stop, uint32_t radix);
extern PNUMBER longtonum(long inlong, uint32_t radix);
extern PNUMBER Ulongtonum(unsigned long inlong, uint32_t radix);
extern PNUMBER numtonRadixx(PNUMBER a, uint32_t radix);

// creates a empty/undefined rational representation (p/q)
extern PRAT _createrat( void );

// returns a new rat structure with the acos of x->p/x->q taking into account
// angle type
extern void acosanglerat( _Inout_ PRAT *px, ANGLE_TYPE angletype, uint32_t radix, int32_t precision);

// returns a new rat structure with the acosh of x->p/x->q
extern void acoshrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the acos of x->p/x->q
extern void acosrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the asin of x->p/x->q taking into account
// angle type
extern void asinanglerat( _Inout_ PRAT *px, ANGLE_TYPE angletype, uint32_t radix, int32_t precision);

extern void asinhrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);
// returns a new rat structure with the asinh of x->p/x->q

// returns a new rat structure with the asin of x->p/x->q
extern void asinrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the atan of x->p/x->q taking into account
// angle type
extern void atananglerat( _Inout_ PRAT *px, ANGLE_TYPE angletype, uint32_t radix, int32_t precision);

// returns a new rat structure with the atanh of x->p/x->q
extern void atanhrat( _Inout_ PRAT *px, int32_t precision);

// returns a new rat structure with the atan of x->p/x->q
extern void atanrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the cosh of x->p/x->q
extern void coshrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the cos of x->p/x->q
extern void cosrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the cos of x->p/x->q taking into account
// angle type
extern void cosanglerat( _Inout_ PRAT *px, ANGLE_TYPE angletype, uint32_t radix, int32_t precision);

// returns a new rat structure with the exp of x->p/x->q this should not be called explicitly.
extern void _exprat( _Inout_ PRAT *px, int32_t precision);

// returns a new rat structure with the exp of x->p/x->q
extern void exprat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the log base 10 of x->p/x->q
extern void log10rat( _Inout_ PRAT *px, int32_t precision);

// returns a new rat structure with the natural log of x->p/x->q
extern void lograt( _Inout_ PRAT *px, int32_t precision);

extern PRAT longtorat( long inlong );
extern PRAT Ulongtorat( unsigned long inulong );
extern PRAT numtorat( _In_ PNUMBER pin, uint32_t radix);

extern void sinhrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);
extern void sinrat( _Inout_ PRAT *px );

// returns a new rat structure with the sin of x->p/x->q taking into account
// angle type
extern void sinanglerat( _Inout_ PRAT *px, ANGLE_TYPE angletype, uint32_t radix, int32_t precision);

extern void tanhrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);
extern void tanrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);

// returns a new rat structure with the tan of x->p/x->q taking into account
// angle type
extern void tananglerat( _Inout_ PRAT *px, ANGLE_TYPE angletype, uint32_t radix, int32_t precision);

extern void _dupnum(_In_ PNUMBER dest, _In_ PNUMBER src);

extern void _destroynum( _In_ PNUMBER pnum );
extern void _destroyrat( _In_ PRAT prat );
extern void addnum( _Inout_ PNUMBER *pa, _In_ PNUMBER b, uint32_t radix);
extern void addrat( _Inout_ PRAT *pa, _In_ PRAT b, int32_t precision);
extern void andrat( _Inout_ PRAT *pa, _In_ PRAT b, uint32_t radix, int32_t precision);
extern void divnum( _Inout_ PNUMBER *pa, _In_ PNUMBER b, uint32_t radix, int32_t precision);
extern void divnumx( _Inout_ PNUMBER *pa, _In_ PNUMBER b, int32_t precision);
extern void divrat( _Inout_ PRAT *pa, _In_ PRAT b, int32_t precision);
extern void fracrat( _Inout_ PRAT *pa , uint32_t radix, int32_t precision);
extern void factrat( _Inout_ PRAT *pa, uint32_t radix, int32_t precision);
extern void modrat( _Inout_ PRAT *pa, _In_ PRAT b );
extern void gcdrat( _Inout_ PRAT *pa, int32_t precision);
extern void intrat( _Inout_ PRAT *px, uint32_t radix, int32_t precision);
extern void mulnum( _Inout_ PNUMBER *pa, _In_ PNUMBER b, uint32_t radix);
extern void mulnumx( _Inout_ PNUMBER *pa, _In_ PNUMBER b );
extern void mulrat( _Inout_ PRAT *pa, _In_ PRAT b, int32_t precision);
extern void numpowlong( _Inout_ PNUMBER *proot, long power, uint32_t radix, int32_t precision);
extern void numpowlongx( _Inout_ PNUMBER *proot, long power );
extern void orrat( _Inout_ PRAT *pa, _In_ PRAT b, uint32_t radix, int32_t precision);
extern void powrat( _Inout_ PRAT *pa, _In_ PRAT b , uint32_t radix, int32_t precision);
extern void powratNumeratorDenominator(_Inout_ PRAT *pa, _In_ PRAT b, uint32_t radix, int32_t precision);
extern void powratcomp(_Inout_ PRAT *pa, _In_ PRAT b, uint32_t radix, int32_t precision);
extern void ratpowlong( _Inout_ PRAT *proot, long power, int32_t precision);
extern void remnum( _Inout_ PNUMBER *pa, _In_ PNUMBER b, uint32_t radix);
extern void rootrat( _Inout_ PRAT *pa, _In_ PRAT b , uint32_t radix, int32_t precision);
extern void scale2pi( _Inout_ PRAT *px, uint32_t radix, int32_t precision);
extern void scale( _Inout_ PRAT *px, _In_ PRAT scalefact, uint32_t radix, int32_t precision);
extern void subrat( _Inout_ PRAT *pa, _In_ PRAT b, int32_t precision);
extern void xorrat( _Inout_ PRAT *pa, _In_ PRAT b, uint32_t radix, int32_t precision);
extern void lshrat( _Inout_ PRAT *pa, _In_ PRAT b , uint32_t radix, int32_t precision);
extern void rshrat( _Inout_ PRAT *pa, _In_ PRAT b, uint32_t radix, int32_t precision);
extern bool rat_equ( _In_ PRAT a, _In_ PRAT b, int32_t precision);
extern bool rat_neq( _In_ PRAT a, _In_ PRAT b, int32_t precision);
extern bool rat_gt( _In_ PRAT a, _In_ PRAT b, int32_t precision);
extern bool rat_ge( _In_ PRAT a, _In_ PRAT b, int32_t precision);
extern bool rat_lt( _In_ PRAT a, _In_ PRAT b, int32_t precision);
extern bool rat_le( _In_ PRAT a, _In_ PRAT b, int32_t precision);
extern void inbetween( _In_ PRAT *px, _In_ PRAT range, int32_t precision);
extern void trimit( _Inout_ PRAT *px, int32_t precision);
extern void _dumprawrat(_In_ const wchar_t *varname, _In_ PRAT rat, std::wostream& out);
extern void _dumprawnum(_In_ const wchar_t *varname, _In_ PNUMBER num, std::wostream& out);
